package cn.tk.config;

import cn.tk.exception.BadRequestException;
import cn.tk.utils.DateTimeUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by denglw on 2017-12-11.<br/>
 * Desc: mvc控制器
 */
@Configuration
@ConditionalOnWebApplication
public class MvcConfig implements WebMvcConfigurer {
    private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern(DateTimeUtil.DATE);
    private static final DateTimeFormatter TF = DateTimeFormatter.ofPattern(DateTimeUtil.TIME);
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern(DateTimeUtil.DATE_TIME);
    private static final BadRequestException BAD_REQUEST_EXCEPTION = new BadRequestException("非法参数异常");
    @Autowired private SignInterceptor signInterceptor;

    @Bean
    public ObjectMapper objectMapper() {
        JsonFactory factory = new JsonFactoryBuilder().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS).build();
        ObjectMapper om = new ObjectMapper(factory);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DTF));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DF));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(TF));
        om.registerModule(javaTimeModule).registerModule(new ParameterNamesModule()).registerModule(new Jdk8Module());
        om.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        om.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return om;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(httpMessageConverter());
    }

    /**
     * 接口返回结果json序列化.<br/>
     */
    private MappingJackson2HttpMessageConverter httpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setDefaultCharset(StandardCharsets.UTF_8);
        converter.setObjectMapper(objectMapper());
        // 设置中文编码格式
        List<MediaType> mediaTypes = new ArrayList<>(2);
        mediaTypes.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(mediaTypes);
        return converter;
    }

    @Override
    @SuppressWarnings("Convert2Lambda")
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new Converter<String, LocalDate>() {
            @Override
            public LocalDate convert(String source) {
                if (source.length() == 0) {
                    return null;
                }
                if (!DateTimeUtil.isLocalDate(source)) {
                    throw BAD_REQUEST_EXCEPTION;
                }
                return DateTimeUtil.toLocalDate(source);
            }
        });
        registry.addConverter(new Converter<String, LocalTime>() {
            @Override
            public LocalTime convert(String source) {
                if (source.length() == 0) {
                    return null;
                }
                if (!DateTimeUtil.isLocalTime(source)) {
                    throw BAD_REQUEST_EXCEPTION;
                }
                return DateTimeUtil.toLocalTime(source);
            }
        });
        registry.addConverter(new Converter<String, LocalDateTime>() {
            @Override
            public LocalDateTime convert(String source) {
                if (source.trim().length() == 0) {
                    return null;
                }
                if (!DateTimeUtil.isLocalDateTime(source)) {
                    throw BAD_REQUEST_EXCEPTION;
                }
                return DateTimeUtil.toLocalDateTime(source);
            }
        });
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LocalDateArgResolver());
        resolvers.add(new LocalTimeArgResolver());
        resolvers.add(new LocalDateTimeArgResolver());
    }

    /**
     * java.time时间类型入参解析.<br/>
     */
    private static Object resolveJavaTimeArg(MethodParameter parameter, NativeWebRequest webRequest) {
        Class<?> paramType = parameter.getParameterType();
        String paramName = parameter.getParameterName();
        assert paramName != null;
        String date = webRequest.getParameter(paramName);
        if (date == null || date.length() == 0) {
            return null;
        }
        // 判断localDate localTime localDateTime
        boolean valid = false;
        Object result = null;
        if (paramType == LocalDate.class) {
            valid = DateTimeUtil.isLocalDate(date);
            if (valid) {
                result = DateTimeUtil.toLocalDate(date);
            }
        } else if (paramType == LocalTime.class) {
            valid = DateTimeUtil.isLocalTime(date);
            if (valid) {
                result = DateTimeUtil.toLocalTime(date);
            }
        } else if (paramType == LocalDateTime.class) {
            valid = DateTimeUtil.isLocalDateTime(date);
            if (valid) {
                result = DateTimeUtil.toLocalDateTime(date);
            }
        }
        if (!valid) {
            throw BAD_REQUEST_EXCEPTION;
        }
        return result;
    }


    /**
     * Created by denglw on 2019-01-07.<br/>
     * Desc: LocalDate入参解析器
     */
    private static class LocalDateArgResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterType() == LocalDate.class;
        }

        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
            return MvcConfig.resolveJavaTimeArg(parameter, webRequest);
        }
    }


    /**
     * Created by denglw on 2019-01-08.<br/>
     * Desc: LocalTime入参解析器
     */
    private static class LocalTimeArgResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterType() == LocalTime.class;
        }

        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
            return MvcConfig.resolveJavaTimeArg(parameter, webRequest);
        }
    }


    /**
     * Created by denglw on 2019-01-07.<br/>
     * Desc: LocalDateTime入参解析器
     */
    private static class LocalDateTimeArgResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterType() == LocalDateTime.class;
        }

        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
            return MvcConfig.resolveJavaTimeArg(parameter, webRequest);
        }
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signInterceptor).addPathPatterns("/intelligent-qa/**");
    }
}
